#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <TabLite.h>

extern unsigned int TabLiteBKDRHash(char *str);
extern TabElem *TabLiteGetByHash(TabLite* tab, unsigned long hash);

int TabElemCopyValue(TabElem* dst, TabElem* src)
{
    if (src->tag == TAB_TAG_STR)
    {
        int len = strlen(src->valStr);
        if (len < 1)
        {
            TabErrorSet(TAB_ERROR_ARG);
            return -1;
        }
        len++;
        char *newStr = malloc(len);
        if (!newStr)
        {
            TabErrorSet(TAB_ERROR_NOMEM);
            return -1;
        }
        memset(newStr, 0, len);
        strncpy(newStr, src->valStr, len);
        newStr[len - 1] = '\0';
        char *oldStr = dst->valStr;
        dst->valStr = newStr;
        if (oldStr != NULL && dst->tag == TAB_TAG_STR)
        {
            free(oldStr);
        }
        
        dst->hashKey = src->hashKey;
        dst->tag = src->tag;
    }
    else
    {
        *dst = *src;
    }
    TabErrorSet(TAB_ERROR_OK);
    return 0;
}

static TabLite* __TabLiteSetHash(TabLite* tab, unsigned long hash, TabElem* elem)
{
    elem->hashKey = hash;    /* update elem hash */

    TabElem *oldElem = TabLiteGetByHash(tab, hash);
    if (oldElem != NULL)
    {
        if (TabElemCopyValue(oldElem, elem) < 0)
            return NULL;
        return tab;
    }
    
    TabElem* tmp;
    ForEachTabElem(tab, tmp)
    {
        if (tmp->tag == TAB_TAG_NIL)
        {
            if (TabElemCopyValue(tmp, elem) < 0)
                return NULL;
            return tab;
        }
    }

    if (!tab->next)
    {
        TabLite* newTab = TabLiteNew();
        if (newTab == NULL)
        {
            return NULL;
        }
        if (TabLiteAttach(tab, newTab) < 0)
        {
            TabLiteDel(newTab);
            return NULL;
        }
    }
    return __TabLiteSetHash(tab->next, hash, elem);
}

TabLite* TabLiteSetByKey(TabLite* tab, char* name, TabElem* elem)
{
    if (!tab || !elem || elem->tag == TAB_TAG_NIL || !name || !strlen(name))
    {
        TabErrorSet(TAB_ERROR_ARG);
        return NULL;            
    }
    if ((elem->tag == TAB_TAG_STR && elem->valStr == NULL) ||
        (elem->tag == TAB_TAG_TAB && elem->valTab == NULL))
    {
        TabErrorSet(TAB_ERROR_ARG);
        return NULL;            
    }
    
    unsigned long hashKey = TabLiteBKDRHash(name);
    return __TabLiteSetHash(tab, hashKey, elem);    
}
